/**
 * @file xmpp client.
 * @auther Zhiqiang Wang wang.9264@gmail.com
 * @defgroup xmpp client. as gtalk client.
 *
 * */
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
#include <fcntl.h>
#include "utils.h"
#include "xmppconnect.h"

#define sipxmpp_getstream(__getstream_res__,__getstream_domain__) snprintf(__getstream_res__,SIPXMPP_BUFFER_SIZE,"<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' xml:lang='en' to='%s' version='1.0'>",__getstream_domain__)
#define sipxmpp_getstarttls(__getstarttls_res__) strcpy(__getstarttls_res__,"<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>")
#define sipxmpp_getplainauth(getstream_res,username,password) {char plain_auth_tobase64[100];sprintf(plain_auth_tobase64,"%s%c%s%c%s",username,0,username,0,password);char* plain_auth_base64ed = base64_encode(plain_auth_tobase64,strlen(username)*2 + strlen(password) + 2);sprintf(getstream_res,"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>%s</auth>",plain_auth_base64ed);free(plain_auth_base64ed);} 
#define sipxmpp_getserial(connection,start,serial) {sprintf(serial,"%s_%d",start,connection->serial++);}

typedef int (*sipxmpp_iq_command_response_callback)(xmpp_connection_t *connection,sipxmpp_iq_command_t *iq_command,micro_libxml_xmpp_element_t *element);
struct sipxmpp_iq_command{
	char occupied;
	char serial[30];
	sipxmpp_iq_command_response_callback response_callback;
	void *userdata;
	struct sipxmpp_iq_command *next;
};
static sipxmpp_iq_command_t *xmpp_get_avaiable_sipxmpp_iq_command(xmpp_connection_t *connection);
static void xmpp_release_sipxmpp_iq_command(xmpp_connection_t *connection,sipxmpp_iq_command_t *sipxmpp_iq_command);
static sipxmpp_iq_command_t *xmpp_find_sipxmpp_iq_command(xmpp_connection_t *connection,char *serial);

extern int errno;

static int sipxmpp_read(xmpp_connection_t *connection,char *buffer,int len);
static int sipxmpp_write(xmpp_connection_t *connection,char *buffer);

static int sipxmpp_make_call_with_session(xmpp_connection_t *connection,xmpp_session_t *session);
static int sipxmpp_make_call_result(xmpp_connection_t *connection,sipxmpp_iq_command_t *iq_command,micro_libxml_xmpp_element_t *element);
static int sipxmpp_destroy_session(xmpp_connection_t *connection,xmpp_session_t *session);
/** create a xmpp session. attention:session_id is not set!!*/
static int sipxmpp_create_session(xmpp_connection_t *connection,struct xmpp_session **session);
static int sipxmpp_send_session_candidates(xmpp_connection_t *connection,struct xmpp_session *session);

static int sipxmpp_iq_result_empty_droper(xmpp_connection_t *connection,sipxmpp_iq_command_t *iq_command,micro_libxml_xmpp_element_t *element);
static void sipxmpp_get_rtp_payload(struct ice_rtp_connection *rtp_connection, micro_libxml_xmpp_element_t *description_element);
static int sipxmpp_connection_read(micro_libxml_parser_t *parser,void *userdata);
static int sipxmpp_found_element(micro_libxml_parser_t *parser,void *userdata,micro_libxml_xmpp_element_t *element);

const SSL_METHOD *ssl_method = NULL;

xmpp_connection_t *xmpp_connections = NULL;
int sipxmpp_init()
{
	SSL_load_error_strings();
	SSL_library_init();
	ssl_method = SSLv3_client_method();
	if(!ssl_method)
	{
		LogE("ssl failed method");
		return S_ERROR;
	}
	return 0;
}
int sipxmpp_destroy()
{
	return 0;
}
int sipxmpp_find_connection_by_sip_username(char *sip_username,xmpp_connection_t **xmpp_connection)
{
	xmpp_connection_t *tmp_connection = xmpp_connections;
	while(tmp_connection)
	{
		if(!strncmp(tmp_connection->sip_username,sip_username,SIP_USERNAME_LEN))
		{
			*xmpp_connection = tmp_connection;
			return S_OK;
		}
		tmp_connection = tmp_connection->next;
	}
	return S_ERROR;
}
static void *sipxmpp_do_login(void *obj)
{
	int retcode = 0;
	xmpp_connection_t *connection = (xmpp_connection_t *)obj;
	char *domainstart = strchr(connection->username,'@');
	if(domainstart)
	{
		strncpy(connection->domain,domainstart+1,128);
	}
	else
	{
		LogE("username should have domain!!!!");
		connection->status = SIPXMPP_CONNECTION_STATUS_ERROR;
		gotofinish(1);
	}
	if(connection->fd)
	{
		connection->status = SIPXMPP_CONNECTION_STATUS_ERROR;
		Log("socket already in use.");
		gotofinish(1);
	}

	connection->fd = socket(AF_INET, SOCK_STREAM, 0);
	if (connection->fd < 0)
	{
		connection->status = SIPXMPP_CONNECTION_STATUS_ERROR;
		Log("ERROR opening socket");
		gotofinish(1);
	}
	if (connect(connection->fd,(struct sockaddr *) &connection->serv_addr,sizeof(connection->serv_addr)) < 0)
	{
		LogE("ERROR connecting. error=%s",strerror(errno));
		connection->status = SIPXMPP_CONNECTION_STATUS_DISCONNECTED;
		gotofinish(1);
	}
	sipxmpp_getstream(connection->sendbuffer,connection->domain);
	if(sipxmpp_write(connection,connection->sendbuffer) <=0)
	{
		LogE("error while writing stream starter. error=%s",strerror(errno));
		gotofinish(0);
	}
	connection->readtimeout = 15;

	micro_libxml_parser_t parser = {
		.stopping = &connection->stopping,
		.readbuffer = connection->readbuffer,
		.readbuffer_len = SIPXMPP_BUFFER_SIZE * 2,
		.readbuffer_available_len = 0,
		.read = &sipxmpp_connection_read,
		.found = &sipxmpp_found_element
	};
	micro_libxml_parse_stream(&parser,connection);
	LogI("lightxmpplib_parse_stream exited.");
	connection->status = SIPXMPP_CONNECTION_STATUS_DISCONNECTED;
	sipxmpp_destroy_xmpp_connection(connection);
	finish:
	pthread_exit(0);
	return NULL;
}
static void sipxmpp_get_rtp_payload(struct ice_rtp_connection *rtp_connection, micro_libxml_xmpp_element_t *description_element)
{
	micro_libxml_attribute_t *payload_name;
	micro_libxml_attribute_t *payload_id;
	description_element = description_element->childs;
	while(description_element)
	{
		if(!xmpp_cmpstr(description_element->element_name,"payload-type",description_element->element_name_len) && (payload_name = micro_libxml_findattribute(description_element,"name"))
				&& (payload_id = micro_libxml_findattribute(description_element,"id")))
		{
			if(!xmpp_cmpstr(payload_name->attribute_value,"PCMU",payload_name->attribute_value_len))
			{
				rtp_connection->payload_type_pcmu = mem_atoi(payload_id->attribute_value,payload_id->attribute_value_len);
				LogD("pcmu payload:[%d]",rtp_connection->payload_type_pcmu);
			}
			else if(!xmpp_cmpstr(payload_name->attribute_value,"telephone-event",payload_name->attribute_value_len))
			{
				rtp_connection->payload_type_telephone_event = mem_atoi(payload_id->attribute_value,payload_id->attribute_value_len);
				LogD("telephone-event:[%d]",rtp_connection->payload_type_telephone_event);
			}
			else
			{
				LogD("unknown payload.name =[%.*s]",payload_name->attribute_value_len,payload_name->attribute_value);
			}
		}
		description_element = description_element->next;
	}
}
static int sipxmpp_remote_set_iq(xmpp_connection_t *connection,micro_libxml_xmpp_element_t *element)
{
	micro_libxml_xmpp_element_t *element_set_childs = element->childs;
	micro_libxml_xmpp_element_t *tmp;
	micro_libxml_attribute_t *tmp_attribute = NULL;
	while(element_set_childs)
	{
		if(!xmpp_cmpstr(element_set_childs->element_name,"session",element_set_childs->element_name_len))
		{
			if((tmp_attribute = micro_libxml_findattribute(element_set_childs,"type"))&& !xmpp_cmpstr(tmp_attribute->attribute_value,"candidates",tmp_attribute->attribute_value_len) && (tmp_attribute = micro_libxml_findattribute(element_set_childs,"id")))
			{
				xmpp_session_t *session = connection->sessions;

				while(session)
				{
					if(session->occupied && !xmpp_cmpstr(tmp_attribute->attribute_value,session->sessionid,tmp_attribute->attribute_value_len))
					{
						tmp = element_set_childs->childs;
						while(tmp)
						{
							//find all "udp" candidates.
							if((tmp_attribute = micro_libxml_findattribute(tmp,"protocol")) && !xmpp_cmpstr(tmp_attribute->attribute_value,"udp",tmp_attribute->attribute_value_len))
							{
								char remote_address[20];
								char remote_port[10];
								if((tmp_attribute = micro_libxml_findattribute(tmp,"address")) != NULL) snprintf(remote_address,20,"%.*s",tmp_attribute->attribute_value_len,tmp_attribute->attribute_value);
								if((tmp_attribute = micro_libxml_findattribute(tmp,"port")) != NULL) snprintf(remote_port,10,"%.*s",tmp_attribute->attribute_value_len,tmp_attribute->attribute_value);
								if((tmp_attribute = micro_libxml_findattribute(tmp,"username")) != NULL) snprintf(session->rtp_connection->remote_username,17,"%.*s",tmp_attribute->attribute_value_len,tmp_attribute->attribute_value);
								if((tmp_attribute = micro_libxml_findattribute(tmp,"password")) != NULL) snprintf(session->rtp_connection->remote_password,17,"%.*s",tmp_attribute->attribute_value_len,tmp_attribute->attribute_value);
								resolv_ip(remote_address,atoi(remote_port),&(session->rtp_connection->serv_addr));
								break;//since we use first candidate. we break this loop
							}
							tmp = tmp->next;
						}
						return 0;
					}
					session = session->next;
				}
				LogE("candidates:we did not find the session. do nothing.");
				return 0;
			}
			else if((tmp_attribute = micro_libxml_findattribute(element_set_childs,"type")) && !xmpp_cmpstr(tmp_attribute->attribute_value,"accept",tmp_attribute->attribute_value_len) && (tmp_attribute = micro_libxml_findattribute(element_set_childs,"id")))
			{
				xmpp_session_t *session = connection->sessions;
				while(session)
				{
					if(session->rtp_connection && !xmpp_cmpstr(tmp_attribute->attribute_value,session->sessionid,tmp_attribute->attribute_value_len)
					 && (tmp = element_set_childs->childs) != NULL && !xmpp_cmpstr(tmp->element_name,"description",tmp->element_name_len))
					{
						sipxmpp_get_rtp_payload(session->rtp_connection, tmp);

						connection->session_status_change(connection,session,SIPXMPP_SESSION_STATUS_ACCEPTED);
						session->rtp_connection->last_negotiation_time = 1;
						return 0;
					}
					session = session->next;
				}
				LogE("accept:did not find session. do nothing.");
			}
			else if((tmp_attribute = micro_libxml_findattribute(element_set_childs,"type")) && !xmpp_cmpstr(tmp_attribute->attribute_value,"terminate",tmp_attribute->attribute_value_len) && (tmp_attribute = micro_libxml_findattribute(element_set_childs,"id")))
			{
				xmpp_session_t *session = connection->sessions;
				while(session)
				{
					if(!xmpp_cmpstr(tmp_attribute->attribute_value,session->sessionid,tmp_attribute->attribute_value_len))
					{
						LogD("found session.set session terminate.");
						stop_rtp_connection(session->rtp_connection);
						connection->session_status_change(connection,session,SIPXMPP_SESSION_STATUS_TERMINATED);
						sipxmpp_destroy_session(connection,session);
						return 0;
					}
					session = session->next;
				}
				LogE("terminate:did not find session. do nothing.");
			}
			else if((tmp_attribute = micro_libxml_findattribute(element_set_childs,"type")) && !xmpp_cmpstr(tmp_attribute->attribute_value,"initiate",tmp_attribute->attribute_value_len) && (tmp_attribute = micro_libxml_findattribute(element_set_childs,"id")))
			{
				xmpp_session_t *session = NULL;
				char error = 0;
				sipxmpp_create_session(connection,&session);
				snprintf(session->sessionid,255,"%.*s",tmp_attribute->attribute_value_len,tmp_attribute->attribute_value);
				if((tmp_attribute = micro_libxml_findattribute(element,"from")) != NULL) snprintf(session->remote,255,"%.*s",tmp_attribute->attribute_value_len,tmp_attribute->attribute_value);
				if((tmp_attribute = micro_libxml_findattribute(element,"to")) != NULL) snprintf(session->local,255,"%.*s",tmp_attribute->attribute_value_len,tmp_attribute->attribute_value);
				if((tmp_attribute = micro_libxml_findattribute(element_set_childs,"initiator")) != NULL) snprintf(session->initiator,255,"%.*s",tmp_attribute->attribute_value_len,tmp_attribute->attribute_value);
				if(session->rtp_connection && element_set_childs->childs && !xmpp_cmpstr(element_set_childs->childs->element_name,"description",element_set_childs->childs->element_name_len))
				{
					sipxmpp_get_rtp_payload(session->rtp_connection, element_set_childs->childs);
				}
				sipxmpp_send_session_candidates(connection,session);
				connection->session_status_change(connection,session,SIPXMPP_SESSION_STATUS_REQUEST);
			}
			else if((tmp_attribute = micro_libxml_findattribute(element_set_childs,"type")))
			{
				LogE("unknown type=%.*s",tmp_attribute->attribute_value_len,tmp_attribute->attribute_value);
			}
		}
		element_set_childs = element_set_childs->next;
	}
	return 0;

}
static int sipxmpp_found_element(micro_libxml_parser_t *parser,void *userdata,micro_libxml_xmpp_element_t *element)
{
	int retcode = 0;
	micro_libxml_xmpp_element_t *tmp = NULL;
	micro_libxml_attribute_t *tmp_attribute = NULL;
	xmpp_connection_t *connection= (xmpp_connection_t*)userdata;
	if(!xmpp_cmpstr(element->element_name,"features",element->element_name_len) && (tmp = element->childs))
	{
		while(tmp)
		{
			if(!xmpp_cmpstr(tmp->element_name,"starttls",tmp->element_name_len))
			{
				sipxmpp_getstarttls(connection->sendbuffer);
				if(sipxmpp_write(connection,connection->sendbuffer) <=0)
				{
					LogE("error while writing stream starter. error=%s",strerror(errno));
					gotofinish(MICRO_LIBXML_FOUND_ELEMENT_RETURN_ERROR);
				}
				else
				{
					gotofinish(MICRO_LIBXML_FOUND_ELEMENT_RETURN_FREE_ROOT);
				}
			}
			else if(!xmpp_cmpstr(tmp->element_name,"mechanisms",tmp->element_name_len) && (tmp = tmp->childs))
			{
				while(tmp)
				{
					if(!xmpp_cmpstr(tmp->element_name,"mechanism",tmp->element_name_len) && !xmpp_cmpstr(tmp->element_value,"PLAIN",tmp->element_value_len))
					{
						sipxmpp_getplainauth(connection->sendbuffer,connection->username,connection->password);
						if(sipxmpp_write(connection,connection->sendbuffer) <=0)
						{
							LogE("error while writing stream starter. error=%s",strerror(errno));
							gotofinish(MICRO_LIBXML_FOUND_ELEMENT_RETURN_ERROR);
						}
						else
						{
							gotofinish(MICRO_LIBXML_FOUND_ELEMENT_RETURN_FREE_CURRENT_ELEMENT);
						}
					}
					tmp = tmp->next;
				}
				gotofinish(MICRO_LIBXML_FOUND_ELEMENT_RETURN_ERROR);
			}
			else if(!xmpp_cmpstr(tmp->element_name,"bind",tmp->element_name_len))
			{
				retcode = sip_xmpp_send_bind(connection);
				if(retcode)
				{
					gotofinish(MICRO_LIBXML_FOUND_ELEMENT_RETURN_ERROR);
				}
				else
				{
					gotofinish(MICRO_LIBXML_FOUND_ELEMENT_RETURN_FREE_CURRENT_ELEMENT);
				}
			}
			tmp = tmp->next;
		}
		gotofinish(MICRO_LIBXML_FOUND_ELEMENT_RETURN_ERROR);
	}
	else if(!xmpp_cmpstr(element->element_name,"proceed",element->element_name_len))
	{

		connection -> ssl = SSL_new(connection -> context);
		if(!connection -> ssl)
		{
			LogE("ssl failed");
			gotofinish(MICRO_LIBXML_FOUND_ELEMENT_RETURN_ERROR);
		}
		SSL_set_fd(connection -> ssl,connection->fd);
		SSL_connect(connection -> ssl);

		sipxmpp_getstream(connection->sendbuffer,connection->domain);
		if(sipxmpp_write(connection,connection->sendbuffer) <=0)
		{
			LogE("error while writing stream starter. error=%s",strerror(errno));
			gotofinish(MICRO_LIBXML_FOUND_ELEMENT_RETURN_ERROR);
		}
		else
		{
			gotofinish(MICRO_LIBXML_FOUND_ELEMENT_RETURN_FREE_ROOT);
		}

	}
	else if(!xmpp_cmpstr(element->element_name,"success",element->element_name_len) && (tmp_attribute = micro_libxml_findattribute(element,"xmlns")) && !xmpp_cmpstr(tmp_attribute->attribute_value,"urn:ietf:params:xml:ns:xmpp-sasl",tmp_attribute->attribute_value_len))
	{
		sipxmpp_getstream(connection->sendbuffer,connection->domain);
		if(sipxmpp_write(connection,connection->sendbuffer) <=0)
		{
			LogE("error while writing stream starter. error=%s",strerror(errno));
			gotofinish(MICRO_LIBXML_FOUND_ELEMENT_RETURN_ERROR);
		}
		else
		{
			gotofinish(MICRO_LIBXML_FOUND_ELEMENT_RETURN_FREE_ROOT);
		}
	}
	else if(!xmpp_cmpstr(element->element_name,"failure",element->element_name_len) && (tmp_attribute = micro_libxml_findattribute(element,"xmlns")) && !xmpp_cmpstr(tmp_attribute->attribute_value,"urn:ietf:params:xml:ns:xmpp-sasl",tmp_attribute->attribute_value_len))
	{
		LogE("login failed");
		connection->status = SIPXMPP_CONNECTION_STATUS_ERROR;
		gotofinish(MICRO_LIBXML_FOUND_ELEMENT_RETURN_ERROR);
	}
	else if(!xmpp_cmpstr(element->element_name,"iq",element->element_name_len))
	{

		char id[255],type[20];
		connection->readtimeout = 300;
		sipxmpp_iq_command_t *request_iq_command;
		if((tmp_attribute = micro_libxml_findattribute(element,"id")) != NULL) snprintf(id,255,"%.*s",tmp_attribute->attribute_value_len,tmp_attribute->attribute_value);
		if((tmp_attribute = micro_libxml_findattribute(element,"type")) != NULL) snprintf(type,20,"%.*s",tmp_attribute->attribute_value_len,tmp_attribute->attribute_value);
		if(strncmp(type,"set",20) && (request_iq_command = xmpp_find_sipxmpp_iq_command(connection,id)) && request_iq_command->response_callback)
		{
			if(request_iq_command->response_callback(connection,request_iq_command,element))
			{
				gotofinish(MICRO_LIBXML_FOUND_ELEMENT_RETURN_ERROR);
			}
			else
			{
				gotofinish(MICRO_LIBXML_FOUND_ELEMENT_RETURN_FREE_CURRENT_ELEMENT);
			}
		}
		else if(!strncmp(type,"set",20))
		{
			int iq_result;
			iq_result =sipxmpp_remote_set_iq(connection,element);
			if(!iq_result)
			{
				strncpy(connection->sendbuffer,"<iq from='",SIPXMPP_BUFFER_SIZE);
				if((tmp_attribute = micro_libxml_findattribute(element,"to")) != NULL){memset(connection->sendbuffer + strlen(connection->sendbuffer) + tmp_attribute->attribute_value_len,0,1); memcpy(connection->sendbuffer + strlen(connection->sendbuffer),tmp_attribute->attribute_value,tmp_attribute->attribute_value_len);}
				strncpy(connection->sendbuffer + strlen(connection->sendbuffer),"' to='",SIPXMPP_BUFFER_SIZE - strlen(connection->sendbuffer));
				if((tmp_attribute = micro_libxml_findattribute(element,"from")) != NULL){memset(connection->sendbuffer + strlen(connection->sendbuffer) + tmp_attribute->attribute_value_len,0,1); memcpy(connection->sendbuffer + strlen(connection->sendbuffer),tmp_attribute->attribute_value,tmp_attribute->attribute_value_len);}
				snprintf(connection->sendbuffer + strlen(connection->sendbuffer),SIPXMPP_BUFFER_SIZE - strlen(connection->sendbuffer),"' id='%s' type='result' />", id);
				if(sipxmpp_write(connection,connection->sendbuffer) <=0)
				{
					LogE("error while send iq result, error=%s",strerror(errno));
					gotofinish(MICRO_LIBXML_FOUND_ELEMENT_RETURN_ERROR);
				}
				gotofinish(MICRO_LIBXML_FOUND_ELEMENT_RETURN_FREE_CURRENT_ELEMENT);
			}
			gotofinish(MICRO_LIBXML_FOUND_ELEMENT_RETURN_ERROR);
		}
		LogE("can not find original request. expired??");
		gotofinish(MICRO_LIBXML_FOUND_ELEMENT_RETURN_FREE_CURRENT_ELEMENT);
	}
	else if(!xmpp_cmpstr(element->element_name,"presence",element->element_name_len))
	{
		gotofinish(MICRO_LIBXML_FOUND_ELEMENT_RETURN_FREE_CURRENT_ELEMENT);
	}
	finish:
		return retcode;
}
int start_sipxmpp_login(xmpp_connection_t *connection)
{
	LogD("sipxmpp start login.");
	connection->status = SIPXMPP_CONNECTION_STATUS_TRYING;
	if(pthread_create(&connection->reading_thread,NULL,sipxmpp_do_login,connection))
	{
		LogE("create xmpp listen thread error");
	}
	return S_OK;
}

int sipxmpp_create_xmpp_connection(char *server,int port,char *username,char *password,char *sip_username,xmpp_connection_t **connection)
{
	int retcode = 0;
	*connection = sipxmpp_calloc(sizeof(xmpp_connection_t));
	if(resolv_ip(server,port,&(*connection)->serv_addr))
	{
		Log("get address error.");
		gotofinish(1);
	}
	strncpy((*connection)->sip_username,sip_username,SIP_USERNAME_LEN);
	strncpy((*connection)->username,username,128);
	strncpy((*connection)->password,password,128);
	(*connection)->context = SSL_CTX_new((SSL_METHOD *)ssl_method);
	if(!(*connection)->context)
	{
		ERR_print_errors_fp(stderr);
		LogE("ssl failed context");
		gotofinish(MICRO_LIBXML_FOUND_ELEMENT_RETURN_ERROR);
	}
	(*connection)->sendbuffer = sipxmpp_calloc(SIPXMPP_BUFFER_SIZE);
	(*connection)->readbuffer = sipxmpp_calloc(SIPXMPP_BUFFER_SIZE * 2);
	(*connection)->serial=1;

	(*connection)->sipxmpp_iq_commands = NULL;
	pthread_mutex_init(&(*connection)->sipxmpp_iq_commands_mutex,NULL);
	if(!xmpp_connections)
	{
		xmpp_connections = *connection;
		LogD("xmpp_connections is set.");
	}
	else
	{
		xmpp_connection_t *tmp_connection = xmpp_connections;
		while(tmp_connection->next) tmp_connection = tmp_connection->next;
		tmp_connection->next = *connection;
	}
	finish:
		if(retcode)
		{
			chkndfree((*connection)->sendbuffer);
			chkndfree((*connection)->readbuffer);
			chkndfree(*connection);
		}
		return retcode;
}
int sipxmpp_destroy_xmpp_connection(xmpp_connection_t *connection)
{
	xmpp_session_t *tmp_session;
	xmpp_connection_t *tmp_connection;
	sipxmpp_iq_command_t *tmp_iq_command;
	if(connection->reading_thread)
	{
		connection->stopping = 1;
		LogI("join connection->reading_thread");
		pthread_join(connection->reading_thread,NULL);
		LogI("join success");
	}
	LogD("destroying sessions");
	while(connection->sessions)
	{
		tmp_session = connection->sessions->next;
		sipxmpp_destroy_session(connection,connection->sessions);
		chkndfree(connection->sessions);
		connection->sessions = tmp_session;
	}
	LogD("sessions destroyed.");

	pthread_mutex_lock(&connection->sipxmpp_iq_commands_mutex);
	LogD("destroying iq_commands.");
	while(connection->sipxmpp_iq_commands)
	{
		tmp_iq_command = connection->sipxmpp_iq_commands->next;
		chkndfree(connection->sipxmpp_iq_commands);
		connection->sipxmpp_iq_commands = tmp_iq_command;
	}
	LogD("iq_commands destroied");
	pthread_mutex_unlock(&connection->sipxmpp_iq_commands_mutex);
	pthread_mutex_destroy(&connection->sipxmpp_iq_commands_mutex);

	if(connection->ssl)
	{
		LogD("free ssl connection.");
		SSL_free(connection->ssl);
		connection->ssl = NULL;
	}
	LogD("close connection fd.");
	//close socket
	if(connection->fd)
	{
		close(connection->fd);
		connection->fd = 0;
	}
	LogD("free ssl context");
	if(connection->context)
	{
		SSL_CTX_free(connection->context);
		connection->context = NULL;
	}
	chkndfree(connection->sendbuffer);
	chkndfree(connection->readbuffer);
	if(connection == xmpp_connections)
		xmpp_connections = xmpp_connections->next;
	else{
		tmp_connection = xmpp_connections;
		while(tmp_connection->next)
		{
			if(tmp_connection->next == connection)
			{
				tmp_connection->next = connection->next;
				break;
			}
			tmp_connection = xmpp_connections;
		}
	}
	chkndfree(connection);
	return S_OK;
}

/**
	@brief read data from xmpp connection.
	@return readed count.
*/
static int sipxmpp_read(xmpp_connection_t *connection,char *buffer,int len)
{
	int count;
	if(connection->ssl)
	{
		count = SSL_read(connection->ssl,buffer,len);
		if(count > 1)
		{
			LogD("<---------\n%.*s",count,buffer);
		}
		return count;
	}
	else
	{
		count = read(connection->fd,buffer,len);
		LogD("<---------\n%.*s",count,buffer);
		return count;
	}
}
static int sipxmpp_write(xmpp_connection_t *connection,char *buffer)
{
	Log("--------->\n%s",buffer);
	if(connection->ssl) return SSL_write(connection -> ssl,buffer,strlen(buffer));
	else return write(connection->fd,buffer,strlen(buffer));
}



/*
int sipxmpp_get_jingleinfo(xmpp_connection_t *connection)
{
	char serial[30];
	int retcode = 0;
	sipxmpp_getserial(connection,"iq_get_jingleinfo",serial);
	xmpp_package_buffer_t *package_buffer = xmpp_get_avaiable_package_buffer();
	sprintf(package_buffer->buffer,"<iq type='get' from='%s' to='%s' id='%s'><query xmlns='google:jingleinfo'/></iq>",connection->jid,connection->username,serial);
	//TODO sipxmpp_get_jingleinfo should use response. but currently, we dont care.
	//jingleinfo return the stun server. which will use by sipxmpp_create_session.
	retcode = sip_xmpp_simple_send_and_wait_result(connection,serial,package_buffer->buffer);
	xmpp_release_package_buffer(package_buffer);
	return retcode;
}*/
static int sipxmpp_send_bind_result(xmpp_connection_t *connection,sipxmpp_iq_command_t *iq_bind_command,micro_libxml_xmpp_element_t *element)
{
	int retcode = 0;
	micro_libxml_xmpp_element_t *tmp = element->childs;
	while(tmp)
	{
		if(!xmpp_cmpstr(tmp->element_name,"bind",tmp->element_name_len))
		{
			tmp = tmp->childs;
			while(tmp)
			{
				if(!xmpp_cmpstr(tmp->element_name,"jid",tmp->element_name_len))
				{
					snprintf(connection->jid,128,"%.*s",tmp->element_value_len,tmp->element_value);
					LogI("jid=[%s]",connection->jid);
					connection->status = SIPXMPP_CONNECTION_STATUS_LOGINED;
					sipxmpp_presence(connection);
					gotofinish(0);
				}
				tmp = tmp->next;
			}
			LogE("can not find jid in bind result");
			gotofinish(1);
		}
		LogE("this is bind result. but element is error.");
		tmp = tmp->next;
	}
	finish:
	xmpp_release_sipxmpp_iq_command(connection,iq_bind_command);
	return retcode;
}
int sip_xmpp_send_bind(xmpp_connection_t *connection)
{
	char serial[30];
	int retcode = 0;

	sipxmpp_getserial(connection,"iq_send_bind",serial);

	snprintf(connection->sendbuffer,SIPXMPP_BUFFER_SIZE,"<iq type='set' id='%s'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></iq>",serial);
	sipxmpp_iq_command_t *iq_bind_command = xmpp_get_avaiable_sipxmpp_iq_command(connection);
	strncpy(iq_bind_command->serial,serial,30);
	iq_bind_command->response_callback = &sipxmpp_send_bind_result;

	if(sipxmpp_write(connection,connection->sendbuffer) <=0)
	{
		LogD("error while send bind. error=%s",strerror(errno));
		gotofinish(1);
	}
	finish:
		return retcode;
}
static int sipxmpp_send_accept_result(xmpp_connection_t *connection,sipxmpp_iq_command_t *iq_command,micro_libxml_xmpp_element_t *element)
{
	xmpp_session_t *session = (xmpp_session_t*)iq_command->userdata;
	if(session->rtp_connection)
	{
		session->rtp_connection->dtmf_to_send_after_ice_success = 1;
		session->rtp_connection->dtmf_to_send_if_no_data = 1;
		session->rtp_connection->last_negotiation_time = 1;
	}
	xmpp_release_sipxmpp_iq_command(connection,iq_command);
	return 0;
}
int sip_xmpp_send_accept(xmpp_connection_t *connection,xmpp_session_t *session)
{
	char serial[30];
	int retcode = 0;

	sipxmpp_getserial(connection,"iq_send_accept",serial);
	snprintf(connection->sendbuffer,SIPXMPP_BUFFER_SIZE,"<iq from='%s' to='%s' type='set' id='%s'><session type='accept' id='%s' initiator='%s' xmlns='http://www.google.com/session'><description xmlns='http://www.google.com/session/phone'><payload-type id='0' name='PCMU' clockrate='8000' bitrate='64000'/><payload-type id='101' name='telephone-event' clockrate='8000'/></description></session></iq>"
		,session->local,session->remote,serial,session->sessionid,session->initiator);
	sipxmpp_iq_command_t *iq_accept_command = xmpp_get_avaiable_sipxmpp_iq_command(connection);
	strncpy(iq_accept_command->serial,serial,30);
	iq_accept_command->userdata = session;
	iq_accept_command->response_callback = &sipxmpp_send_accept_result;

	if(sipxmpp_write(connection,connection->sendbuffer) <=0)
	{
		LogE("error while writing stream starter. error=%s",strerror(errno));
		gotofinish(1);
	}
	finish:
		return retcode;
}
int sipxmpp_pong(xmpp_connection_t *connection,sipxmpp_iq_command_t *iq_command,micro_libxml_xmpp_element_t *element)
{
	LogI("pong.......");
	xmpp_release_sipxmpp_iq_command(connection,iq_command);
	return S_OK;
}
int sipxmpp_ping(xmpp_connection_t *connection)
{
	int retcode = 0;
	char serial[30];
	sipxmpp_getserial(connection,"iq_ping",serial);
	snprintf(connection->sendbuffer,SIPXMPP_BUFFER_SIZE,"<iq from='%s' to='%s' id='%s' type='get'><ping xmlns='urn:xmpp:ping' /></iq>",connection->jid,connection->domain,serial);

	sipxmpp_iq_command_t *iq_ping_command = xmpp_get_avaiable_sipxmpp_iq_command(connection);
	strncpy(iq_ping_command->serial,serial,30);
	iq_ping_command->response_callback = &sipxmpp_pong;

	if(sipxmpp_write(connection,connection->sendbuffer) <=0)
	{
		LogE("error while send ping. error=%s",strerror(errno));
		gotofinish(1);
	}
	gotofinish(0);

	finish:
		return retcode;
}
/*
static int sipxmpp_discovery(xmpp_connection_t *connection)
{
	int retcode = 0;
	char serial[30];
	char *sendbuffer = sipxmpp_get_send_buffer(connection);
	sipxmpp_getserial(connection,"iq_disc",serial);
	if(!sendbuffer)
	{
		Log("error while get connection->sendbuffer");
		gotofinish(1);
	}
	snprintf(sendbuffer,SIPXMPP_BUFFER_SIZE,"<iq type='get' to='gmail.com' id='%s'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>",serial);
	if(sipxmpp_write(connection,sendbuffer) <=0)
	{
		Log("error while send presence. error=%s",strerror(errno));
		sipxmpp_release_send_buffer(connection);
		gotofinish(1);
	}
	snprintf(sendbuffer,SIPXMPP_BUFFER_SIZE," ");
	if(sipxmpp_write(connection,sendbuffer) <=0)
	{
		Log("error while send an empty char. error=%s",strerror(errno));
		sipxmpp_release_send_buffer(connection);
		gotofinish(1);
	}
	sipxmpp_release_send_buffer(connection);

	gotofinish(0);

	finish:
		return retcode;
}
*/
int sipxmpp_presence(xmpp_connection_t *connection)
{

	int retcode = 0;
	snprintf(connection->sendbuffer,SIPXMPP_BUFFER_SIZE,"<presence><c node='http://mail.google.com/xmpp/client/caps' ver='1.1' ext='voice-v1' xmlns='http://jabber.org/protocol/caps'/><priority>1</priority><status>Voice Only</status><x xmlns='vcard-temp:x:update'><photo>146c020749be602df2e9988ab014ed7fb73d9344</photo></x></presence>");
	if(sipxmpp_write(connection,connection->sendbuffer) <=0)
	{
		LogE("error while send presence. error=%s",strerror(errno));
		gotofinish(1);
	}
	gotofinish(0);
	finish:
		return retcode;
}

static int sipxmpp_send_session_candidates(xmpp_connection_t *connection,struct xmpp_session *session)
{
	char serial[30],stuned_ip[20];
	int stuned_port;
	int retcode = 0;

	if(stun_request_ip(session->rtp_connection->fd,"stun.l.google.com",19302,stuned_ip,20,&stuned_port))
	{
		gotofinish(1);
	}
	sipxmpp_getserial(connection,"iq_send_candidates",serial);

	snprintf(connection->sendbuffer,SIPXMPP_BUFFER_SIZE,"<iq from='%s' to='%s' type='set' id='%s'><session type='candidates' id='%s' initiator='%s' xmlns='http://www.google.com/session'><candidate name='rtp' address='%s' port='%d' username='%s' preference='1' protocol='udp' type='stun' network='0' generation='0'/><transport xmlns='http://www.google.com/transport/p2p'/></session></iq>"
			,session->local,session->remote,serial,session->sessionid,session->initiator,stuned_ip,stuned_port,session->rtp_connection->local_username);
	sipxmpp_iq_command_t *iq_command = xmpp_get_avaiable_sipxmpp_iq_command(connection);
	strncpy(iq_command->serial,serial,30);
	iq_command->userdata = session;
	iq_command->response_callback = &sipxmpp_iq_result_empty_droper;
	if(sipxmpp_write(connection,connection->sendbuffer) <=0)
	{
		LogE("error while senc candidates. error=%s",strerror(errno));
		gotofinish(1);
	}
	finish:
	return retcode;
}
static int sipxmpp_make_call_with_session(xmpp_connection_t *connection,xmpp_session_t *session)
{
	char serial[30];
	int retcode = 0,count=0;
	struct xmpp_element *tmp = NULL;

	sipxmpp_getserial(connection,"iq_initiate",serial);
	snprintf(connection->sendbuffer,SIPXMPP_BUFFER_SIZE,"<iq type='set' from='%s' to='%s' id='%s'><session xmlns='http://www.google.com/session' type='initiate' initiator='%s' id='%s'><description xmlns='http://www.google.com/session/phone' xml:lang='en'><payload-type id='0' name='PCMU' clockrate='8000' bitrate='64000'/><payload-type id='101' name='telephone-event' clockrate='8000'/></description></session></iq>",
	session->local,session->remote,serial,session->initiator,session->sessionid);

	sipxmpp_iq_command_t *iq_command = xmpp_get_avaiable_sipxmpp_iq_command(connection);
	strncpy(iq_command->serial,serial,30);
	iq_command->userdata = session;
	iq_command->response_callback = &sipxmpp_make_call_result;

	if(sipxmpp_write(connection,connection->sendbuffer) <=0)
	{
		LogE("error while writing stream starter. error=%s",strerror(errno));
		gotofinish(1);
	}
	finish:
		return retcode;
}
//reason: success,busy,decline,unsupported-transports,unsupported-applications..... see http://xmpp.org/extensions/xep-0166.html#session-terminate
int sipxmpp_terminate_call(xmpp_connection_t *connection,xmpp_session_t *session,const char* reason)
{
	char serial[30];
	int retcode = 0;
	sipxmpp_getserial(connection,"iq_terminate",serial);

	snprintf(connection->sendbuffer,SIPXMPP_BUFFER_SIZE,"<iq from='%s' to='%s' id='%s' type='set'><ses:session type='terminate' id='%s' initiator='%s' xmlns:ses='http://www.google.com/session'><ses:reason><ses:%s/><ses:text>Call ended</ses:text></ses:reason><pho:call-ended xmlns:pho='http://www.google.com/session/phone'/></ses:session></iq>",
		session->local,session->remote,serial,session->sessionid,session->initiator,reason);
	if(sipxmpp_write(connection,connection->sendbuffer) <=0)
	{
		LogE("error while writing stream starter. error=%s",strerror(errno));
		gotofinish(1);
	}
	finish:
	sipxmpp_destroy_session(connection,session);
	return retcode;
}
/**
 * create a xmpp session[mostly a voice session]
 * @param connection   the connection
 * @param session	the created session
 * @return session result. 0:success 1: fail
 * */
static int sipxmpp_create_session(xmpp_connection_t *connection,struct xmpp_session **session)
{
	unsigned char randusername[8];
	char stuned_ip[20];
	sipxmpp_createrandomdata((char *)randusername,sizeof(randusername));

	xmpp_session_t *tmp_session = connection->sessions;
	while(tmp_session)
	{
		if(!tmp_session->occupied)
		{
			(*session) = tmp_session;
			goto session_created;
		}
	}
	*session = sipxmpp_calloc(sizeof(struct xmpp_session));
	if(!connection->sessions)
		connection->sessions = (*session);
	else
	{
		xmpp_session_t *tmp_session = connection->sessions;
		while(tmp_session->next)
			tmp_session = tmp_session->next;
		tmp_session->next = (*session);
	}

	session_created:
	(*session)->occupied = 1;
	(*session)->rtp_connection = create_new_rtp_connection(NULL);
	string2hex(randusername,sizeof(randusername),(*session)->rtp_connection->local_username);
	return 0;
}
/**
 * destroy a xmpp session [destroy after a voice session.]
 * @param connection the connection
 * @param session the session to destroy. the caller should set it null
 * @return result. always return 0.
 * */
static int sipxmpp_destroy_session(xmpp_connection_t *connection,xmpp_session_t *session)
{
	if(session->rtp_connection)
	{
		LogD("destroy it's rtp connection.");
		destroy_rtp_connection(session->rtp_connection);
	}
	memset(session,0,sizeof(xmpp_session_t) - sizeof(xmpp_session_t *));
	return 0;
}
/**
 * make a voice call.
 * @param connection the xmpp connection.
 * @param to the number to call. caller should add domain to it.
 * @param session.
 * @return the created session. or NULL is failed.
 * */
xmpp_session_t *sipxmpp_make_call(xmpp_connection_t *connection,const char *to,char *sessionid)
{
	xmpp_session_t *session = NULL;
	sipxmpp_create_session(connection,&session);
	strncpy(session->sessionid,sessionid,255);
	strncpy(session->remote,to,255);
	strncpy(session->local,connection->jid,255);
	strncpy(session->initiator,connection->jid,255);
	if(sipxmpp_make_call_with_session(connection,session))
	{
		sipxmpp_destroy_session(connection,session);
		return NULL;
	}
	else
	{
		return session;
	}
}
static int sipxmpp_iq_result_empty_droper(xmpp_connection_t *connection,sipxmpp_iq_command_t *iq_command,micro_libxml_xmpp_element_t *element)
{
	LogI("got response for:[%s]", iq_command->serial);
	xmpp_release_sipxmpp_iq_command(connection,iq_command);
	return 0;
}
static int sipxmpp_make_call_result(xmpp_connection_t *connection,sipxmpp_iq_command_t *iq_command,micro_libxml_xmpp_element_t *element)
{
	int retcode = 0;
	micro_libxml_attribute_t *tmp_attribute = micro_libxml_findattribute(element,"type");
	micro_libxml_xmpp_element_t *tmp;
	xmpp_session_t *session = (xmpp_session_t*)iq_command->userdata;
	if(tmp_attribute && !xmpp_cmpstr(tmp_attribute->attribute_value,"error",tmp_attribute->attribute_value_len) && (tmp = element->childs))
	{
		char c_errorcode[10];
		int errorcode = 0;
		while(tmp)
		{
			if(!xmpp_cmpstr(tmp->element_name,"error",tmp->element_name_len) && (tmp_attribute = micro_libxml_findattribute(tmp,"code")))
			{
				micro_libxml_xmpp_element_t *redirect_element;
				snprintf(c_errorcode,10,"%.*s",tmp_attribute->attribute_value_len,tmp_attribute->attribute_value);
				errorcode = atoi(c_errorcode);
				redirect_element = tmp->childs;
				while(redirect_element)
				{
					if(!xmpp_cmpstr(redirect_element->element_name,"redirect",redirect_element->element_name_len))
					{
						int skipbytes = 0;
						if(!xmpp_cmpstr(redirect_element->element_value,"xmpp:",5)) skipbytes = 5;
						snprintf(session->remote,255,"%.*s",redirect_element->element_value_len - skipbytes,redirect_element->element_value + skipbytes);
						break;
					}
					redirect_element = redirect_element->next;
				}
			}
			tmp = tmp->next;
		}
		if(errorcode >= 300 && errorcode < 400)
		{
			char serial[30];
			sipxmpp_getserial(connection,"iq_initiate",serial);

			snprintf(connection->sendbuffer,SIPXMPP_BUFFER_SIZE,"<iq type='set' from='%s' to='%s' id='%s'><session xmlns='http://www.google.com/session' type='initiate' initiator='%s' id='%s'><description xmlns='http://www.google.com/session/phone' xml:lang='en'><payload-type id='0' name='PCMU' clockrate='8000' bitrate='64000'/><payload-type id='101' name='telephone-event' clockrate='8000'/></description></session></iq>",
						session->local,session->remote,serial,session->initiator,session->sessionid);

			sipxmpp_iq_command_t *reinvite_iq_command = xmpp_get_avaiable_sipxmpp_iq_command(connection);
			strncpy(reinvite_iq_command->serial,serial,30);
			reinvite_iq_command->userdata = session;
			reinvite_iq_command->response_callback = &sipxmpp_make_call_result;

			if(sipxmpp_write(connection,connection->sendbuffer) <=0)
			{
				Log("error while writing stream starter. error=%s",strerror(errno));
				gotofinish(1);
			}
			gotofinish(0);
		}
	}
	else if(tmp_attribute && !xmpp_cmpstr(tmp_attribute->attribute_value,"result",tmp_attribute->attribute_value_len))
	{
		sipxmpp_send_session_candidates(connection,session);
		gotofinish(0);
	}
	xmpp_release_sipxmpp_iq_command(connection,iq_command);
	finish:
	return retcode;
}

static sipxmpp_iq_command_t *xmpp_get_avaiable_sipxmpp_iq_command(xmpp_connection_t *connection)
{
	pthread_mutex_lock(&connection->sipxmpp_iq_commands_mutex);
	sipxmpp_iq_command_t *sipxmpp_iq_command = connection->sipxmpp_iq_commands;
	while(sipxmpp_iq_command)
	{
		if(!sipxmpp_iq_command->occupied)
		{
			goto finish;
		}
		sipxmpp_iq_command = sipxmpp_iq_command->next;
	}

	sipxmpp_iq_command = sipxmpp_calloc(sizeof(sipxmpp_iq_command_t));
	if(!connection->sipxmpp_iq_commands)
	{
		connection->sipxmpp_iq_commands = sipxmpp_iq_command;
	}
	else
	{
		sipxmpp_iq_command_t *tmp_iq_command = connection->sipxmpp_iq_commands;
		while(tmp_iq_command->next)
		{
			tmp_iq_command = tmp_iq_command->next;
		}
		tmp_iq_command->next = sipxmpp_iq_command;
	}
	finish:
	sipxmpp_iq_command->occupied = 1;
	pthread_mutex_unlock(&connection->sipxmpp_iq_commands_mutex);
	return sipxmpp_iq_command;

}
static void xmpp_release_sipxmpp_iq_command(xmpp_connection_t *connection,sipxmpp_iq_command_t *sipxmpp_iq_command)
{
	pthread_mutex_lock(&connection->sipxmpp_iq_commands_mutex);
	sipxmpp_iq_command_t *tmp_iq_command;
	tmp_iq_command = connection->sipxmpp_iq_commands;
	while(tmp_iq_command)
	{
		if(tmp_iq_command == sipxmpp_iq_command)
		{
			memset(tmp_iq_command,0,sizeof(sipxmpp_iq_command_t) - sizeof(sipxmpp_iq_command_t *));
			break;
		}
		tmp_iq_command = tmp_iq_command->next;
	}
	pthread_mutex_unlock(&connection->sipxmpp_iq_commands_mutex);
}
static sipxmpp_iq_command_t *xmpp_find_sipxmpp_iq_command(xmpp_connection_t *connection,char *serial)
{
	pthread_mutex_lock(&connection->sipxmpp_iq_commands_mutex);
	sipxmpp_iq_command_t *tmp_iq_command = connection->sipxmpp_iq_commands;
	while(tmp_iq_command)
	{
		if(tmp_iq_command->occupied && !strncmp(tmp_iq_command->serial,serial,30))
		{
			pthread_mutex_unlock(&connection->sipxmpp_iq_commands_mutex);
			return tmp_iq_command;
		}
		tmp_iq_command = tmp_iq_command->next;
	}
	pthread_mutex_unlock(&connection->sipxmpp_iq_commands_mutex);
	return NULL;
}

static int sipxmpp_connection_read(micro_libxml_parser_t *parser,void *userdata)
{
	int					count;
	fd_set				fd_set_v;
	struct timeval		tval;
	xmpp_connection_t *connection = (xmpp_connection_t *)userdata;
	int timeout = connection->readtimeout;
	if(parser->readbuffer_len - parser->readbuffer_available_len < 100)
	{
		LogE("not enough memory. there should be some tags we dont know how to release.");
		return -1;
	}
	memset(parser->readbuffer + parser->readbuffer_available_len,0,parser->readbuffer_len - parser->readbuffer_available_len);

	while(!connection->stopping && timeout > 0)
	{
		FD_ZERO(&fd_set_v);
		FD_SET(connection->fd,&fd_set_v);
		tval.tv_sec = 1;
		tval.tv_usec = 0;
		if(select(FD_SETSIZE, &fd_set_v, NULL, NULL, &tval) && FD_ISSET(connection->fd,&fd_set_v))
		{
			count = sipxmpp_read(connection,parser->readbuffer + parser->readbuffer_available_len,parser->readbuffer_len - parser->readbuffer_available_len);
			if(count == -1)
			{
				if(EAGAIN != errno)
				{
					LogE("Receive error,result=%d,desc[%s]",count,strerror(errno));
					break;
				}
				continue;
			}
			if(count == 1 && *(parser->readbuffer + parser->readbuffer_available_len) == ' ')
			{
				continue;
			}
			parser->readbuffer_available_len += count;
			return count;
		}
		--timeout;
	}
	LogI("read timeout");
	return 0;
}

